package doc.jmidifile;


import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class JMidiTrack extends JMidiChunk implements Serializable {
    // 本类设计的目的就是为了业务方便包装一些控制 track 的接口
    // 所以 events 属性设计成 public
    public List<JMidiEvent> events = new ArrayList<>();

    public JMidiTrack() {
        super(TRACK_ID);
    }

    /**
     * 获取音轨的时长
     *
     * @return >= 0 音轨的时长
     */
    public long getDuration() {
        int s = events.size();
        if (s == 0) {
            return -1;
        }
        return events.get(s - 1).ms;
    }

    /**
     * 向音轨后面添加数据。
     *
     * @param event 待添加的数据。只有当 event.ms >= 本声轨最后一个 event的时间的时候才能添加
     * @return
     */
    public boolean add( JMidiEvent event) {
        int s = events.size();
        if (s == 0 || events.get(s - 1).ms <= event.ms) {
            return events.add(event);
        }
        return false;
    }

    /**
     * 解析 midi 文件用的构造函数
     */
    JMidiTrack(final byte[] data, int[] off) throws InvalidParameterException {
        super(data, off);
        // 检测 track 头
        if (!Arrays.equals(TRACK_ID, this.id)) {
            makeInvalidMidiFileException();
        }

        // 解析 event
        int cursor = 0;
        int abstick = 0;
        JMidiChunk.Var var = new JMidiChunk.Var();
        int tick;
        int[] runningEventStatus = new int[]{0};

        while (cursor < this.size) {
            // 读取 tick
            tick = var.readValue(data, off[0] + cursor, size - cursor);
            if (tick < 0) {
                makeInvalidMidiFileException();
            }
            abstick += tick;
            cursor += var.len;
            // 读取 event
            cursor += readEvent(data, off[0] + cursor, abstick, runningEventStatus, size - cursor);
        }

        // 去除 track 结束 event
        int i = events.size();
        while (i > 0) {
            i -= 1;
            JMidiEvent e = events.get(i);
            if (e.getEventType() == JMidiEvent.TYPE_META && e.getMetaType() == JMidiEvent.TYPE_META_TRACK_END) {
                events.remove(i);
            } else {
                break;
            }
        }

        // 调整偏移量
        off[0] += this.size;
    }

    void save(BufferedOutputStream stream) throws IOException {
        // 先将要写的数据都放到内存，计算长度后修改 size 大小
        ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
        int tick = 0;
        byte runningStatus = 0;
        Var var = new Var();
        for (JMidiEvent e : events) {
            // 写 delta tick
            tick = e.tick - tick;
            out.write(var.writeData(tick));
            tick = e.tick;
            // 写入事件
            writeEvent(out, e, runningStatus);
            runningStatus = e.data[0];
        }
        // 写入音轨结束
        out.write(var.writeData(0));
        JMidiEvent trackEndEvent = new JMidiEvent(0);
        trackEndEvent.setTrackEnd();
        writeMetaEvent(out, trackEndEvent);

        // 再写文件头和全部数据
        this.size = out.size();
        super.save(stream);
        stream.write(out.toByteArray());
    }

    private static void writeEvent(ByteArrayOutputStream stream, final JMidiEvent e, byte runningStatus) throws IOException {
        int t = e.getEventType();
        switch (t) {
            case JMidiEvent.TYPE_META:
                writeMetaEvent(stream, e);
                return;
            case JMidiEvent.TYPE_SYSEX0:
            case JMidiEvent.TYPE_SYSEX1:
                writeSysexEvent(stream, e);
                return;
            default:
                writeCommonEvent(stream, e, runningStatus);
                return;
        }
    }

    private static void writeMetaEvent(ByteArrayOutputStream stream, final JMidiEvent e) throws IOException {
        // 写入的格式为 0xff <meta type> vv ...
        Var var = new Var();
        stream.write(e.data, 0, 2);
        stream.write(var.writeData(e.data.length - 2));
        stream.write(e.data, 2, e.data.length - 2);
    }

    private static void writeSysexEvent(ByteArrayOutputStream stream, final JMidiEvent e) throws IOException {
        // 写入的格式为 0xf0 vv ...
        Var var = new Var();
        stream.write(e.data, 0, 1);
        stream.write(var.writeData(e.data.length - 1));
        stream.write(e.data, 1, e.data.length - 1);
    }

    private static void writeCommonEvent(ByteArrayOutputStream stream, final JMidiEvent e, byte runningStatus) throws IOException {
        // 使用 running status 来减小文件大小
        if (runningStatus == e.data[0]) {
            stream.write(e.data, 1, e.data.length - 1);
        } else {
            stream.write(e.data);
        }
    }

    /**
     * 读取一个event
     *
     * @return 读取了多少字节的数据
     */
    private int readEvent(final byte[] data, int offset, int tick, int[] runningEventStatus, int lenLimit) throws InvalidParameterException {
        if (lenLimit < 1) {
            makeInvalidMidiFileException();
        }

        int status = data[offset] & 0xff;
        int cursor = 0;

        if (status < 0x80) {
            if (runningEventStatus[0] == 0) {
                // 出现了没有 running event status 的 event status
                makeInvalidMidiFileException();
            }
            status = runningEventStatus[0];
        } else {
            if (status != JMidiEvent.TYPE_META) {
                // meta event 不设置 running status
                runningEventStatus[0] = status;
            }
            cursor = 1;
        }
        lenLimit -= cursor;
        offset += cursor;

        // 判断status 的类型
        if (status >= 0xf0) {
            switch (status) {
                case JMidiEvent.TYPE_META:
                    cursor += readMetaEvent(data, offset, tick, lenLimit);
                    break;
                case JMidiEvent.TYPE_SYSEX0:
                case JMidiEvent.TYPE_SYSEX1:
                    cursor += readSysexEvent(data, offset, tick, lenLimit);
                    break;
                default:
                    // 不支持解析的事件
                    makeInvalidMidiFileException();
                    break;
            }
        } else {
            cursor += readCommonEvent(data, offset, tick, status, lenLimit);
        }
        return cursor;
    }

    private int readMetaEvent(final byte[] data, int offset, int tick, int lenLimit) throws InvalidParameterException {
        if (lenLimit < 1) {
            makeInvalidMidiFileException();
        }
        byte type = data[offset];
        int cursor = 1;
        offset += cursor;
        lenLimit -= cursor;

        // 读取数据长度
        JMidiChunk.Var var = new JMidiChunk.Var();
        int val = var.readValue(data, offset, lenLimit);
        if (var.len == 0) {
            makeInvalidMidiFileException();
        }
        cursor += var.len;
        lenLimit -= var.len;
        offset += var.len;
        if (lenLimit < val) {
            makeInvalidMidiFileException();
        }
        cursor += val;

        // 生成event
        JMidiEvent e = new JMidiEvent(tick, new byte[val + 2]);
        e.data[0] = (byte) JMidiEvent.TYPE_META;
        e.data[1] = type;
        System.arraycopy(data, offset, e.data, 2, val);
        events.add(e);
        return cursor;
    }

    private int readSysexEvent(final byte[] data, int offset, int tick, int lenLimit) throws InvalidParameterException {
        int cursor = 0;
        JMidiChunk.Var var = new JMidiChunk.Var();
        int val = var.readValue(data, offset, lenLimit);
        if (var.len == 0) {
            makeInvalidMidiFileException();
        }
        cursor = var.len;
        offset += cursor;
        lenLimit -= cursor;

        if (lenLimit < val) {
            makeInvalidMidiFileException();
        }
        cursor += val;
        // 删除头尾数据
        if ((data[offset] & 0xff) == 0xf0) {
            val -= 1;
            offset += 1;
        }
        if ((data[offset + val - 1] & 0xff) == 0xf7) {
            val -= 1;
        }
        // 生成 event
        JMidiEvent e = new JMidiEvent(tick, new byte[val + 2]);
        e.data[0] = (byte) JMidiEvent.TYPE_SYSEX0;
        System.arraycopy(data, offset, e.data, 1, val);
        e.data[val + 1] = (byte) JMidiEvent.TYPE_SYSEX1;
        events.add(e);

        return cursor;
    }

    private int readCommonEvent(final byte[] data, int offset, int tick, int status, int lenLimit) throws InvalidParameterException {
        switch (status & 0xf0) {
            // 2 bytes event
            case JMidiEvent.TYPE_NOTE_ON:
            case JMidiEvent.TYPE_NOTE_OFF:
            case JMidiEvent.TYPE_AFTER_TOUCH:
            case JMidiEvent.TYPE_CONTROLLER:
            case JMidiEvent.TYPE_PITCH_BEND:
                if (lenLimit < 2) makeInvalidMidiFileException();
                events.add(new JMidiEvent(tick, (byte) status, data[offset], data[offset + 1]));
                return 2;

            // 1 byte events
            case JMidiEvent.TYPE_TIMBRE:
            case JMidiEvent.TYPE_PRESSURE:
                if (lenLimit < 1) makeInvalidMidiFileException();
                events.add(new JMidiEvent(tick, (byte) status, data[offset]));
                return 1;

            default:
                // 其它的指令应当是不存储于mid文件的
                return 0;
        }
    }

}
